<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>俄罗斯方块</title>
    <style>
        body {
            display: flex;
        }
        .mf {
            border: 1px solid #ddd;
            display: flex;
            flex-wrap: wrap;
            position: relative;
            margin:0 auto;
        }
        .mf.fail:after {
            content: '游戏失败';
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            font-size: 30px;
        }
        .item {
            box-sizing: border-box;
            border: 1px solid #ddd;
        }
        .box {
            background-color: #444;
        }
        .over {
            background-color:rosybrown;
        }
    </style>
</head>
<body>
    <div class="mf"></div>
    <script type="module">
        /**
         * 1、地图创建
         * 2、俄罗斯方块的形状数据
         * 3、下一个方块
         * 4、方块位置和角度变换
         * 5、方块是否落地
         * 6、消除已成行的方块
         * 7、实现render函数来串联这一切
         */
        // 定义俄罗斯方块的各种形状
        import boxList from './9.js'
        class Game {
            // 多少行
            rows = 16
            // 多少列
            cols = 12
            // 地图格子大小
            size = 30
            // 按下去的按键
            key = ''
            // 下一个俄罗斯方块
            box =  []
            // 时间参数，用来计算游戏速度的
            time = Date.now()
            // 方向形态
            dirValue = 0
            mf
            constructor(selector) {
                this.box = this.getBox(boxList)
                this.mf = document.querySelector(selector)
                this.createMap()
                this.render()
            }
            // 创建地图
            createMap() {
                const mf = document.querySelector('.mf')
                const { rows, cols, size } = this
                const result = []
                for(let i=0;i<rows;i++) {
                    for(let j=0;j<cols;j++) {
                        const isEat = Math.random() > 0.9
                        result.push(`<div data-v="${i}-${j}" class="item" style="width:${size}px;height:${size}px"></div>`)
                    }
                }
                // 绘制地图
                mf.innerHTML = result.join('')
                // 设置地图宽
                mf.style.width = `${cols * size}px`

                // 记录按下去的是哪个按键
                document.addEventListener('keydown', e=> {
                    this.key = e.key
                })
                // 松开时清空
                document.addEventListener('keyup', e=> {
                    this.key = ''
                })
            }
            // 随机获取下一个俄罗斯方块
            getBox(list) {
                const box = list[parseInt(Math.random() * list.length)]
                return JSON.parse(JSON.stringify(box))
            }
            // 渲染函数
            render() {
                const {box, dirValue, key, time, rows, cols} = this
                // 获取当前俄罗斯方块的方向形态
                const _box = box.slice(dirValue * 4, (dirValue + 1) * 4)
                // 按下a健或d健时的位移变量，a移动-1，d移动1，也就是左右移动
                const leftV = {
                    a: -1,
                    d: 1        
                }
                // 是否按下了w键
                let topV = 0
                // 是否按下了s键
                let downV = 0
            
                if (key === 'a') {
                    // 如果左侧碰到了墙壁，则设置位移变量值为0，即移不动
                    if (_box.filter(v => {
                        return v[1] - 1 < 0 || this.isOver([v[0], v[1] - 1])
                    }).length > 0) {
                        leftV[key] = 0
                    }
                } else if (key === 'd') {
                    // 如果右侧碰到了墙壁，则设置位移变量值为0，即移不动
                    if (_box.filter(v => v[1] + 1 > cols - 1 || this.isOver([v[0], v[1] + 1])).length > 0) {
                        leftV[key] = 0
                    }
                } else if (key === 's') {
                    downV = 1
                } else if (key === 'w') {
                    topV = 1
                }
                let result = -1
                // 如果是按了wasd按键
                if (topV || downV || leftV[key]) {
                    // 如果按下的不是s键，那么就设置为100ms更新一次
                    if (!downV && Date.now() - this.time > 100) {
                        result = this.updateBox(box, downV, leftV[key])
                        this.time = Date.now()
                    } else if (downV) {
                        // 如果按下的是s建，则以最快速度更新
                        result = this.updateBox(box, downV, leftV[key])
                    }
                } else {
                    // 否则就是自然下落，设置为500ms更新一次
                    if (Date.now() - this.time > 500) {
                        result = this.updateBox(box, 1, 0)
                        this.time = Date.now()
                    }
                }
                // result=0表示，当前的俄罗斯方块已经落下完成
                if (result === 0) {
                    // 俄罗斯方块的方向形态重置为0
                    this.dirValue = 0
                    // 下一个俄罗斯更快开始准备
                    this.box = this.getBox(boxList)
                    // 清理已经拼好的俄罗斯方块
                    this.clearSuccess()
                    
                } else if (result === 2) {
                    // 游戏失败
                    this.mf.classList.add('fail')
                    return
                }

                requestAnimationFrame(this.render.bind(this))
            }
            // 更新俄罗斯方块状态
            updateBox(boxAll, top, left = 0) {
                const { key, dirValue, rows } = this
                // 如果按w，表示开始切换俄罗斯方向
                if (key === 'w') {
                    const max = boxAll.length / 4
                    this.dirValue++
                    if (this.dirValue >= max) {
                        this.dirValue = 0
                    }
                }
                const _box = boxAll.slice(this.dirValue * 4, (this.dirValue + 1) * 4)
                // 更新之前，先清除掉所有的运行中的俄罗斯方块，目的是清理上一次的俄罗斯方块形态
                document.querySelectorAll('.box').forEach(v => v.classList.remove('box'))
                // 检查当前的俄罗斯方块，是否已经满足了停止条件
                const isOver = _box.filter(v => {
                    // 找到同列下一行的格子，查看是否有over的class样式，有则代表碰到了其他已落地的俄罗斯块
                    const nextRow = document.querySelector(`[data-v="${v[0]+1}-${v[1]}"]`)
                    const isStop = nextRow && nextRow.className.indexOf('over') >= 0
                    // 到达了最后一行 或 碰到了其他已落地的俄罗斯方块
                    return v[0] >= rows - 1 || isStop
                }).length >= 1
                
                // 如果满足的停止条件
                if (isOver) {
                    // 当前的俄罗斯方块位置，是不是在第一行，如果是，则表示游戏结束
                    if (_box.filter(v => v[0] <= 0).length > 0) {
                        return 2
                    }
                    // 如果不是，就表示是正常落下，那么就俄罗斯方块由运行中设置为已落地
                    _box.forEach(v => {
                        const el = document.querySelector(`[data-v="${v[0]}-${v[1]}"]`)
                        if (el) {
                            el.classList.add('over')
                        }
                    })
                    return 0
                } 

                // 如果没有满足停止条件，则继续运动
                boxAll.forEach((v, i) => {
                    boxAll[i][0] += top
                    boxAll[i][1] += left
                })
                // 更新运动后的样式信息
                _box.forEach(v => {
                    const el = document.querySelector(`[data-v="${v[0]}-${v[1]}"]`)
                    if (el) {
                        el.classList.add('box')
                    }
                })
                
                return 1
            }
            // 是否是红色块，即已经落地的方块
            isOver(v) {
                const el = document.querySelector(`[data-v="${v.join('-')}"]`)
                // 如果当前个字包含了over，说明是已经落地的方块
                if (el && el.className.indexOf('over') >= 0) {
                    return true
                }
                return false
            }
            // 消除成功的行
            clearSuccess() {
                const {rows, cols} = this
                const jjs = []
                // 找到需要消除的行，记录下行号，并消除
                for(let i = 0; i < rows; i++) {
                    // 找到当前行的所有格子
                    const els = document.querySelectorAll(`[data-v^="${i}-"].over`)
                    let overCount = els.length
                    // 如果当前行包含over的格子等于一行的数量，就表示需要消除
                    if (overCount === cols) {
                        // 消除当前行
                        els.forEach(v => v.classList.remove('over'))
                        // 记录消除行的index
                        jjs.push(i)
                    }
                }
                // 更新所有已落地的俄罗斯方块的位置
                jjs.forEach(v => {
                    // 从最后一行往上找
                    for(let i = rows - 1; i >= 0; i--) {
                        // 如果当前行小于消除的行，那么就需要向下落一行
                        if (i < v) {
                            const els = document.querySelectorAll(`[data-v^="${i}-"].over`)
                            els.forEach(v => {
                                // 消除当前行的颜色
                                v.classList.remove('over')
                                // 找到当前对应下一行的格子
                                const key = v.getAttribute('data-v').replace(/(\d+)-(\d+)/, (a, b, c) => {
                                    return parseInt(b) + 1 + '-' + c
                                })
                                // 给下一行更新颜色
                                document.querySelector(`[data-v="${key}"]`).classList.add('over')
                            })
                        }
                    }
                })
            }
        }
        new Game('.mf')
    </script>
</body>
</html>